import os
import queue
from datetime import datetime
from threading import Thread
from time import sleep

from src.lib.crc16 import crc16Bytes, crc16
from src.lib.errorno import g_errno_d


class BootDfuProtocols():

    rx_buff = queue.Queue()

    # PACK
    UP_PORT_HEAD = "UP^"
    m_i_pack_id = 0
    m_i_pack_data_len = 0
    m_b_pack_data = bytes()
    m_b_pack_crc = bytes()
    UP_PORT_TAIL = "\r\n"

    # data
    m_s_pack_data = bytes()

    # up_file
    m_i_file_size = 0
    m_i_file_crc = 0
    m_i_negation_interval = 0
    m_i_head_info_len = 0
    m_i_send_size_cum = 0

    # up pack size
    m_i_pack_size = 128

    m_s_cmd_prefix = ""
    m_b_cmd_suffix = bytes()

    _success_tag = "UP^OK,"
    _fail_tag = "UP^FAIL,"

    def __init__(self, ui, port):
        super(BootDfuProtocols, self).__init__()
        self.ui = ui
        self.Port = port
        self.MainWindow = self.ui.MainWindow

    def uiSendReceive(self, text):
        self.Port.m_s_current_time = self.Port.getCurrentTime()
        self.Port.Dfu.uiForwardReceive(self.Port.getTimeStrAndText(text))

    def getData(self, redelivery):
        if not redelivery:
            self.m_i_pack_id += 1
            if (self.m_i_pack_id > 255):
                self.m_i_pack_id = 0

        # print("pack_id", self.m_i_pack_id)
        self.m_i_pack_data_len = int(len(self.m_b_pack_data))

        pack_info = bytes([self.m_i_pack_id, self.m_i_pack_data_len])

        self.m_b_pack_crc = crc16Bytes(self.m_b_pack_data, False)
        # print("pack_info",pack_info.hex(), self.m_b_pack_crc.hex())
        # print("pack_data", self.m_b_pack_data.hex())
        return ((self.UP_PORT_HEAD + "DATA=").encode('utf-8') + pack_info +
                self.m_b_pack_data + self.m_b_pack_crc)

    def flagRestart(self):
        self.m_i_pack_id = 0
        print("OTA flagRestart")

    # 获取当前升级进度(%)
    def getUpPercentage(self):
        return (self.m_i_send_size_cum / int(self.m_i_file_size)) * 100

    def loadUpFile(self, file_path):
        self.file = open(file=file_path, mode='rb')
        file_size = os.path.getsize(file_path)
        data = self.file.read(20)
        if len(data) == 0:
            self.file.close()
            return 1
        # 得到头信息
        head_len = -1
        while 1:
            temp_len = data.find(b'||', head_len + 1)
            if temp_len == -1:
                break
            head_len = temp_len
        head_len += (2 + 1)  # || + 间隔数
        head_text = data[:head_len-1].decode()
        head_info = head_text.split('||')
        if len(head_info) != 3:
            return 1
        self.m_i_file_crc = head_info[0]
        self.m_i_file_size = head_info[1]
        self.m_i_head_info_len = head_len
        self.m_i_negation_interval = int.from_bytes(data[head_len-1:head_len], 'little')
        self.m_i_send_size_cum = 0
        # 解密
        self.file.seek(head_len)
        data = self.file.read()
        data_v = bytearray(data)
        if self.m_i_negation_interval != 0:
            len_i = 0
            while 1:
                len_i += self.m_i_negation_interval
                if(len_i >= (len(data_v))):
                    break
                else:
                    # print(len_i, len(data_v), head_len)
                    a = (~(data_v[len_i]) & 0xFF).to_bytes(2, byteorder='big', signed=True)
                    # print(a, len(a), len_i)
                    data_v[len_i] = a[1]
        self.m_d_dfu_data = bytearray(data_v)
        return 0

    def closeUpFile(self):
        self.file.close()
        self.m_i_send_size_cum = 0

    def readyNextPack(self):
        try:
            # self.file.read(self.m_i_pack_size)
            if int(self.m_i_file_size) - (self.m_i_send_size_cum + self.m_i_pack_size) > 0:
                bytes_s = self.m_d_dfu_data[self.m_i_send_size_cum:
                                            self.m_i_send_size_cum + self.m_i_pack_size]
            else:
                bytes_s = self.m_d_dfu_data[self.m_i_send_size_cum:]
            self.m_b_pack_data = bytes_s
            self.m_i_send_size_cum += len(bytes_s)
            # print("ok",len(bytes_s), self.m_i_send_size_cum, int(self.m_i_file_size), len(self.m_d_dfu_data))
        except Exception:
            return g_errno_d['GET']
        if len(bytes_s) == 0:
            # print(len(bytes_s), self.m_i_send_size_cum, int(self.m_i_file_size))
            return g_errno_d['INVAL_LEN']
        # read data completion
        return g_errno_d['OK']

    def BootTaskStart(self):
        # 重置flag
        self.flagRestart()

        # 清空队列
        self.rx_buff.queue.clear()
        '''线程启动(无法双核运行)'''
        self.BootDfuThr = ""
        self.BootDfuThr = Thread(target=self.task_Boot_up, daemon=True)
        self.BootDfuThr.start()

    def task_Boot_up(self):
        print(datetime.now())
        ret = g_errno_d['OK']
        # 清空队列
        self.rx_buff.queue.clear()
        print("bootloader")
        ret, data = self.__subTask_SendCmd_Retry("bootloader".encode(), 5, "CMD[bootloader] ", 0)

        '''发送文件信息命令'''

        # 清空队列
        self.rx_buff.queue.clear()
        print("UP^FILE_INFO=%s,%s" % (self.m_i_file_crc, self.m_i_file_size))
        ret, data = self.__subTask_SendCmd_Retry(("UP^FILE_INFO=%s,%s" %
                                                 (self.m_i_file_crc, self.m_i_file_size)).encode(), 5,
                                                 "CMD[FILE_INFO] ", 0)
        if (ret != g_errno_d['OK']):
            self.Port.Dfu.portUpTaskStop("升级失败!", g_errno_d['FAIL'])
            return
        # 根据返回结果重新定义发包数量
        list_s = data.split(',')
        if (len(list_s) == 3):
            data_len = int(list_s[2]) - len(self.UP_PORT_HEAD + "DATA=" + "\r\n") - 2 - 2 - 2
            if (data_len > 255):
                data_len = 255
            if (data_len <= 0):
                ret = g_errno_d['INVAL_LEN']
            self.m_i_pack_size = data_len
            print("m_i_pack_size", self.m_i_pack_size)

        self.start_time = datetime.now()

        '''发送DFU数据命令'''
        if (ret != g_errno_d['OK']):
            self.Port.Dfu.portUpTaskStop("升级失败!", g_errno_d['FAIL'])
            return
        # 清空队列
        self.rx_buff.queue.clear()
        ret = self.__subTask_SendUpData()

        if self.Port.Dfu.m_i_upgrade_flag == 0:
            ret = g_errno_d['FAIL']

        '''发送DFU文件检验'''
        if ret != g_errno_d['OK']:
            self.Port.Dfu.portUpTaskStop("升级失败!", g_errno_d['FAIL'])
            return

        # 清空队列
        self.rx_buff.queue.clear()
        ret, data = self.__subTask_SendCmd_Retry(("UP^CHECK_FILE=1").encode(), 5,
                                                 "CMD[CHECKFILE] ", 0)

        time_s = str((datetime.now() - self.start_time).total_seconds())[:-3]
        self.uiSendReceive("发送完毕用时:" + time_s + "s")

        print(datetime.now())
        if ret == g_errno_d['OK']:
            self.uiSendReceive("数据发送完毕")
            self.Port.Dfu.portUpTaskStop("升级成功!", g_errno_d['OK'])
        else:
            self.Port.Dfu.portUpTaskStop("升级失败!", g_errno_d['FAIL'])
            if self.Port.Dfu.m_i_upgrade_flag == 1:
                self.Port.Dfu.portUpTaskStop(
                    "Abnormal communication, task stop.", g_errno_d['FAIL'])

    def __subTask_SendUp_Retry(self, data, limit, msg_prefix):
        data = self.m_s_cmd_prefix.encode() + data + "\r\n".encode()
        for i in range(0, limit):
            self.Port.sendMsg(data)
            # print(data.hex())
            try:
                rx_data = self.rx_buff.get(timeout=0.05)
                if rx_data.find(self._success_tag + str(self.m_i_pack_id)) > -1:
                    return g_errno_d['OK']
                else:
                    print("RX_>",rx_data)
                    # 判断是否被分包
                    try:
                        rx_data += self.rx_buff.get()
                        if (rx_data.find(self._success_tag +
                                         str(self.m_i_pack_id)) > -1):
                            return g_errno_d['OK']
                        elif rx_data.find(self._fail_tag) > -1:
                            # 收到错误反馈，重发消息
                            print("err data:", rx_data)
                            self.uiSendReceive(msg_prefix +
                                               "return invalid,resend " +
                                               str(i + 1))
                        else:
                            # 收到无效数据,连续读一段数据，再判断
                            print("invalid data:", rx_data)
                            try:
                                for ii in range(0, 10):
                                    rx_data += self.rx_buff.get_nowait()
                            except Exception:
                                pass
                            if rx_data.find(self._success_tag +
                                             str(self.m_i_pack_id)) > -1:
                                return g_errno_d['OK']
                            elif rx_data.find(self._fail_tag) > -1:
                                # 收到错误反馈，重发消息
                                print("err data:", rx_data)
                                self.uiSendReceive(msg_prefix +
                                                   "return invalid,resend " +
                                                   str(i + 1))
                    except Exception:
                        self.uiSendReceive(msg_prefix +
                                           "return invalid,resend " +
                                           str(i + 1))
            except Exception:
                self.uiSendReceive(msg_prefix + ",rx timeout,resend " +
                                   str(i + 1))
        return g_errno_d['FAIL']

    def __subTask_Fota_Wait_Finish(self):
        for i in range(0, 20):
            rx_data = ""
            try:
                for ii in range(0, 10):
                    rx_data += self.rx_buff.get(timeout=1)
            except Exception:
                pass
            i = rx_data.find("finish")
            if i != -1:
                rx_data = ""
                try:
                    for ii in range(0, 10):
                        rx_data += self.rx_buff.get(timeout=1)
                except Exception:
                    pass
                return g_errno_d['OK']

        return g_errno_d['FAIL']

    def __subTask_SendUpData(self):
        '''向下位机发送升级数据'''
        prefix_s = "CMD[Up Data] "
        self.m_i_pack_id = 0
        old_val = 0
        print("向下位机发送升级数据")
        while 1:
            ret = self.readyNextPack()
            # print("ret", ret)
            if (ret == g_errno_d['OK']):
                data = self.getData(False)
                if (self.m_i_pack_data_len != 0):
                    # print("m_i_pack_id" , self.m_i_pack_id)
                    ret = self.__subTask_SendUp_Retry(data, 10, prefix_s)
                else:
                    ret = g_errno_d['OK']
                    print("up data fail1", self.m_i_pack_data_len)
                    return ret
                if (ret != g_errno_d['OK']):
                    print("up data fail2", self.m_i_pack_data_len)
                    break
                if old_val != self.getUpPercentage():
                    old_val = self.getUpPercentage()
                    self.Port.upBar_signal.emit(old_val)
            else:
                ret = g_errno_d['OK']
                print("up data fail", self.m_i_pack_data_len)
                break
        return ret

    def __subTask_SendCmd_Retry(self, data, limit, msg_prefix, delay):
        data = self.m_s_cmd_prefix.encode() + data
        data += self.m_b_cmd_suffix
        rx_data = ""
        for i in range(0, limit):
            self.Port.sendMsg(data)
            # print(data)
            sleep(0.01)
            try:
                sleep(delay)
                if (delay != 0):
                    try:
                        for ii in range(0, 15):
                            rx_data += self.rx_buff.get_nowait()
                    except Exception:
                        pass
                else:
                    rx_data = self.rx_buff.get(timeout=0.2)
                self.uiSendReceive(rx_data)
                if (rx_data.find("OK") > -1):
                    return g_errno_d['OK'], rx_data
                else:
                    print("invalid data1:", rx_data)
                    self.uiSendReceive(msg_prefix + "return invalid,resend " +
                                       str(i + 1))
            except Exception:
                self.uiSendReceive(msg_prefix + ",rx timeout,resend " +
                                   str(i + 1))
        return g_errno_d['FAIL'], rx_data

    def Task_Start(self, start_time, file_path, prefix, LF):
        # 装载升级文件
        ret = self.loadUpFile(file_path)
        if ret == 1:
            self.Port.Dfu.uiTaskAbort()
            self.Port.msg_signal.emit(3, "提示", "文件解析错误!")
            return
        # 填充前缀
        self.m_s_cmd_prefix = prefix
        if LF:
            self.m_b_cmd_suffix = b'\r\n'
        else:
            self.m_b_cmd_suffix = bytes()
        # 开始boot升级任务
        self.BootTaskStart()
